
/* 
 * This file is part of FreeBMS - an open source battery management system
 *
 * CAN-Enabled Supervisor Microcontroller - PIC18F2585
 * 
 * NOTE: This code is designed to be compiled using SOURCEBOOST (http://www.sourceboost.com)
 */
 
/* This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* 
 * PORT A:
 *
 * | PIN | I/O | Description
 * -----------------------------------
 * | 0   | n/a | n/a
 * | 1   | n/a | n/a
 * | 2   | n/a | n/a
 * | 3   | n/a | n/a
 * | 4   | n/a | n/a
 * | 5   | n/a | n/a
 * | 6   | n/a | n/a
 * | 7   | n/a | n/a
 *
 * PORTB:
 *
 * | PIN | I/O | Description
 * -----------------------------------
 * | 0   | n/a | n/a
 * | 1   | n/a | n/a
 * | 2   | O   | CANTX
 * | 3   | I   | CANRX
 * | 4   | n/a | n/a
 * | 5   | n/a | n/a
 * | 6   | n/a | ICSP Programming
 * | 7   | n/a | ICSP Programming
 * 
 * PORTC:
 * | PIN | I/O | Description
 * -----------------------------------
 * | 0   | n/a | n/a
 * | 1   | n/a | n/a
 * | 2   | n/a | n/a
 * | 3   | n/a | n/a
 * | 4   | n/a | n/a
 * | 5   | n/a | n/a
 * | 6   | n/a | n/a
 * | 7   | n/a | n/a
 *
 */

// Timer preloaders
#define TIMER0_PRELOAD_HI	0x0B	// Timer preloader for timer0: 100ms interval
#define TIMER0_PRELOAD_LO	0xDC	// Timer preloader for timer0: 100ms interval

// Global Includes 
#include <system.h>
#include <string.h>
#include "types.h"
#include "canbus.h"
#include "serial.h"
#include "mappings.h"

// Serial Message Type ID Codes
#define ID_WAKEUP			0x00	// Request chips to wake up
#define ID_START_VOLT		0x01	// Request to start voltage conversion
// EMPTY					0x02
#define ID_VOLT				0x03	// Message request for voltage
// EMPTY					0x04
#define ID_SHUNT			0x05	// Message to enable/disable shunting
#define ID_PROGRAM_ID		0x06	// Message to program the chip ID
#define ID_PROGRAM_ERROR	0x07	// Message to program the error correction percentage (LEAD ACID)
#define ID_PROGRAM_CUTOFFS	0x08	// Message to program the cutoff voltages
#define ID_PROGRAM_TIMERS	0x09	// Message to program the shunting timers
#define ID_PROGRAM_LEDS		0x0A	// Message to program LED configurations
#define ID_PROGRAM_DELAY	0x0B	// Message to program ADC shunting delay

// LED mappings
#define LED1				2		// PORTA
#define LED2				1		// PORTA

// ADC mappings
#define VOLTOUT_CHANNEL		4		// Voltage output channel
#define VOLTIN_CHANNEL		0		// Voltage input channel

// Configuration bits
#pragma DATA _CONFIG1H, _HSPLL_OSC_1H				// HS + PLL Oscillator (4x10 MHz)
#pragma DATA _CONFIG2H, _WDT_OFF_2H & _WDTPS_64_2H  // Watchdog timer controlled by software
													// Postscaler: 64, lower value to speed up
#pragma DATA _CONFIG2L, _BORV_25_2L				 	// Brown-out reset enable, 2.5V trigger
#pragma DATA _CONFIG4L, _STVR_ON_4L & _LVP_OFF_4L	// Stack-overflow chip reset enable
													// Disable low voltage programming

// Global function declarations
unsigned short adc_measure(char channel);

// Global options
#define WDT_ENABLED 			0					// Watchdog enabled
#define MESSAGE_HISTORY			5					// Number of messages to keep track of

// ADC Bits
volatile bit adc_go @ ADCON0 . GO;
volatile bit adc_on @ ADCON0 . ADON;

// Global variables
bool comm_fault;									// Communication failure
bool shunting_fault;								// Shunting failure
bool adc_fault;										// ADC failure
bool stop_polling;									// Boolean to stop polling
unsigned short cell_voltage;						// Voltage of the cell
unsigned short volt_in;								// Voltage going into the cell
unsigned short volt_out;							// Voltage going out from the cell
unsigned long tmp_calc;								// Temporary work variable
BYTE board_num;										// Board number we are testing

// Local shadow variables
BYTE localporta;									// Local shadow of porta
BYTE localportb;									// Local shadow of portb
BYTE localportc;									// Local shadow of portc
BYTE localportd;									// Local shadow of portd
BYTE localporte;									// Local shadow of porte
BYTE localtrisa;									// Local shadow of trisa
BYTE localtrisb;									// Local shadow of trisb
BYTE localtrisc;									// Local shadow of trisc
BYTE localtrisd;									// Local shadow of trisd
BYTE localtrise;									// Local shadow of trise

// High priority interrupt handler
void interrupt(void) {
	unsigned long NewMessage;
	BYTE NMD[8]; // Buffer for new message
	BYTE NewMessageLen;
	BYTE NewMessageDest;
	BYTE i;
	enum CAN_RX_MSG_FLAGS NewMessageFlags;

	// Serial receive message
	if(test_bit(pir1, RCIF)) {

		// Receive and parse message
		if(SerialReceiveMessage()) {
		
			// Get ID and Destination
			NewMessage = rxbuffer[0];
			NewMessageDest = rxbuffer[1];

			// Move rest of buffer down by 2
			for(i = 0; i < 6; ++i)
				NMD[i] = rxbuffer[i+2];

			// Parse request
			switch(rxbuffer[2]) {

				// Regulator has submitted battery voltage
				case ID_VOLT:
					// Pull the voltage from the rxbuffer
					cell_voltage = (rxbuffer[3] << 8) | rxbuffer[4];
					break;
			}
				
			// Only send messages with actual information
			if(rxptr > 0) {
				// Send message
				CANSendMessage_inthi( NewMessage, rxbuffer, rxptr,	CAN_TX_PRIORITY_3 & 
																	CAN_TX_STD_FRAME & 
																	CAN_TX_NO_RTR_FRAME);			
			}
		}		

		// Clear serial receive interrupt
		clear_bit(pir1, RCIF);
	}
	
	// Check if we were interrupted because of a received CAN message
	if ( CANIsRxReady() )
	{
		// Receive CANBUS message for high and low priority buffers
		CANReceiveMessage(&NewMessage, NMD, &NewMessageLen, &NewMessageFlags);

		if ( NewMessageFlags & CAN_RX_OVERFLOW ) {} // RX overflow occurred
		else if ( NewMessageFlags & CAN_RX_INVALID_MSG ) {} // Invalid message received
		else if ( NewMessageFlags & CAN_RX_XTD_FRAME ) {} // Extended Identifier received
		else if ( NewMessageFlags & CAN_RX_RTR_FRAME ) {} // RTR frame received (Request?)
		else // Standard Identifier received
		{
			// Handle CANBUS message
			if(NewMessage == 0x10) {
				set_cell(NMD[0], NMD[1]);
			} else if(NewMessage = 0x20) {		
				board_num = NMD[0];
			} else {
				// Get ID and Destination
				NewMessage = NMD[0];
				NewMessageDest = NMD[1];
				
				// Move rest of buffer down by 2
				for(i = 0; i < 6; ++i)
					NMD[i] = NMD[i+2];

				// Send message
				SerialSendMessage(NewMessage, NewMessageDest, NMD, NewMessageLen, false);
			} 
		}
	}	
	return;
}

// Low priority interrupt handler
void interrupt_low(void) {
	BYTE NMD[8]; // Buffer for new message
	BYTE i, checksum;

	// Check if we were interrupted because of timers
	if(test_bit(intcon, TMR0IF)) { // Timer0: 100ms
		
		// Decrement the rxblock_timer if its active, so we make sure to break out of loops
		if(rxblock_timer > 0) { rxblock_timer--; }

		// Preload timer0 for 100ms interval
		tmr0h = TIMER0_PRELOAD_HI;
		tmr0l = TIMER0_PRELOAD_LO;

		// Clear interrupt flag
		clear_bit(intcon, TMR0IF);
	}

	return;
}

// Main code
void main(void) {
	// Local variables
	unsigned int target_board = 0;
	BYTE NMD[8]; // Buffer for sending messages
	BYTE record_id = 1;
	BYTE chipid = 0;
	BYTE i = 0, checksum = 0;

	// Configure AD converters
	adcon0 = 10000001b; // FOSC/64
//	adcon1 = 11000011b; // Configure digital inputs
	adcon1 = 11001001b; // Configure digital inputs

#if WDT_ENABLED==1
	// Enable watchdog timer
	wdtcon = 00000001b;
#else
	#warning "Watchdog timer is disabled."
	// Disable watchdog timer
	wdtcon = 00000000b;
#endif

	// Initialize communication variables
	rxptr = 0;
	rxlength = 0;
	rxescape = 0;
	rxblock_bit = false;

	// Initialize error variables
	comm_fault = false;
	shunting_fault = false;
	adc_fault = false;
	
	// Initialize global variables
	board_num = 0;
	
	// Trisa:	
	//			AN0  (0) - 1
	//			LED1 (1) - 0
	//			LED2 (2) - 0
	//			AN3  (3) - 1
	//			AN4  (5) - 1
	trisa = 0b00101001;

	// Trisb:	CAN RX (3)	 - 1
	//			CAN TX (2)	 - 0
	trisb = 0b10001000;

	// Trisc: 	Serial RX(7) - 1
	//			Serial TX(6) - 0
	trisc = 0b10000000;
	
	// Initialize trisd and trise as outputs
	trisd = 0x00;
	trise = 0x00;

	// Initialize porta, portb, portc
	porta = 0x00;
	portb = 0x00;
	portc = 0x00;
	portd = 0x00;
	porte = 0x00;

	/* 
	 * Initialize the CAN Interface for the Prius's 500Kbps bus and a 20.000 Mhz crystal:
	 * 
	 * Baud rate registers
	 * Fosc = 20.000 MHz
	 * TQ = (2 * (BRP_ + 1))/FOSC = 2 * 1 / 20 MHz = 100 ns
	 * Nominal CAN bit rate for Prius = 500 KHz; Tbit = 2 us
	 * Therefore, Nominal Bit Time = 2 us / 100 ns = 20 TQ
	 * We divide it as follows:
	 *   Sync: 		 1 TQ (this is fixed)
	 *   Baud Rate Prescaler 0 BRP
	 *   Propagation: 	 3 TQ (the lines lengths are short, so that should be fine) PRSEG
	 *   Phase Segment 1:	 8 TQ (half of the remainder) SEG1PH
	 *   
	 * CANInitialize(SJW, BRP, PHSEG1, PHSEG2, PROPSEG, config);
	 */
	CANInitialize(1, 0, 8, 8, 3, CAN_CONFIG_VALID_STD_MSG); 

	// Initialize chip ID
	chip_id = 0;

	/*
	 * Serial_init - Initialize the serial interface
	 * Arguments: Baudrate scaler
	 */
	serial_init(64);  // 9600 Baud at 40MHz
//	serial_init(32);  // 19200 Baud at 40MHz
	
	// Initialize shadows
	localporta = porta;
	localportb = portb;
	localportc = portc;
	localportd = portd;
	localporte = porte;
	localtrisa = trisa;
	localtrisb = trisb;
	localtrisc = trisc;
	localtrisd = trisd;
	localtrise = trise;

	// Set interrupt priorities
	ipr1 = 0b00100000;			// Serial receive (bit 5) as high priority, all else low
	ipr2 = 0b00000000;			// All interrupts low priority
	ipr3 = 0b00000011;			// CANBUS receive buffers 0,1 as high priority, all else low
	clear_bit(intcon2, TMR0IP);	// Timer0 interrupt - Low
	set_bit(rcon, IPEN); 		// Enable interrupt priorities

	// Initialize timer0
	tmr0h = TIMER0_PRELOAD_HI;	// Preload high byte for timer0: 100ms
	tmr0l = TIMER0_PRELOAD_LO;	// Preload high byte for timer0: 100ms
	t0con = 10010011b;			// 1:16 Prescaler
	set_bit(intcon, TMR0IE);

	// Enable system interrupts for receiving messages
	set_bit(pie3, RXB0IE);
	set_bit(pie3, RXB1IE);
	set_bit(intcon, GIE);
	set_bit(intcon, PEIE);

	// Enable serial interrupt
	set_bit(pie1, RCIE);

	unsigned short safety_timer = 1800;
	unsigned short max_voltage = 3625;
	BYTE adc_delay = 1;
	BYTE led_mode = 0x00;
	BYTE cell;
	
	// Main application loop
	while(1) {
		// Clear watchdog timer
		clear_wdt();
	
		// Output registers have somehow been overridden, very dangerous
		if(trisa != localtrisa
		|| trisb != localtrisb
		|| trisc != localtrisc
		|| trisd != localtrisd
		|| trise != localtrise) {
	
			// Attempt to restore registers quickly
			trisa = localtrisa;
			trisb = localtrisb;
			trisc = localtrisc;
			trisd = localtrisd;
			trise = localtrise;
	
			// Reset the chip to avoid any unpleasent surprises
			asm
			{
				RESET
			}
		}

		// Clear LEDs
		clear_bit(localporta, LED1);
		clear_bit(localporta, LED2);
		porta = localporta;
	
		// Wait for a board number before continuing
		while(board_num == 0) {
			clear_wdt();
			
			// Flash LEDs
			toggle_bit(localporta, LED1);
			toggle_bit(localporta, LED2);
			porta = localporta;
			
			delay_s(5);
		}
		
		// Wait for system to start up
		delay_s(10);
	
		// Initialize variables
		stop_polling = false;
	
		// Clear LEDs
		clear_bit(localporta, LED1);
		clear_bit(localporta, LED2);
		porta = localporta;
	
		// Loop through all cells on board
		for(cell = 1; cell < 21; ++cell) {
			// Clear errors
			comm_fault = false;
			shunting_fault = false;
			adc_fault = false;
			cell_voltage = 0;

			// Monitor for cancel message
			if(stop_polling == true) {
				board_num = 0;
				break;
			}

			// Set chip ID	
			chipid = cell + ((board_num - 1) * 20);
	
			// Turn on the power to the cell
			clear_wdt();
			delay_s(5);
			set_cell(cell, true);
	
			// Set 3.3v power supply
			set_bit(localportb, 5);
			portb = localportb;
		
			// Send wake-up call
			SerialSendMessage(ID_WAKEUP, 0x00, NMD, 0x00, false);
			delay_ms(5);
		
			// Program chip ID
			NMD[0] = 1;
			NMD[1] = 2;
			NMD[2] = 3;
			NMD[3] = 4;
			SerialSendMessage(ID_PROGRAM_ID, chipid, NMD, 4, true);
			delay_s(10);
			clear_wdt();
		
			// Program safety cutoff values
			NMD[0] = 1;
			NMD[1] = (max_voltage >> 8) & 0x00FF;
			NMD[2] = (max_voltage & 0x00FF);
			SerialSendMessage(ID_PROGRAM_CUTOFFS, chipid, NMD, 3, true);
			delay_s(10);
			clear_wdt();	
			
			// Program safety timers
			NMD[0] = 1;
			NMD[1] = (safety_timer >> 8) & 0x00FF;
			NMD[2] = (safety_timer & 0x00FF);
			SerialSendMessage(ID_PROGRAM_TIMERS, chipid, NMD, 3, true);
			delay_s(10);
			clear_wdt();

			// Program ADC delay
			NMD[0] = 1;
			NMD[1] = adc_delay;
			SerialSendMessage(ID_PROGRAM_DELAY, chipid, NMD, 2, true);
			delay_s(10);
			clear_wdt();

			// Program LED mode
			NMD[0] = 1;
			NMD[1] = led_mode;
			SerialSendMessage(ID_PROGRAM_LEDS, chipid, NMD, 2, true);
			delay_s(10);
			clear_wdt();
						
			// Call for an AD conversion from the cell
			NMD[0] = 1;
			SerialSendMessage(ID_START_VOLT, 0x00, NMD, 1, false);
			delay_s(5);
			clear_wdt();
			
			// Request stored voltage from cell
			NMD[0] = 1;
			SerialSendMessage(ID_VOLT, chipid, NMD, 1, true);
			delay_s(5);
			clear_wdt();
	
			// Determine if the adc_fault flag needs to be set
			if(cell_voltage < 3250 || cell_voltage > 3350)
				adc_fault = true;
	
			// Request cell start shunting
			NMD[0] = 1;
			SerialSendMessage(ID_SHUNT, chipid, NMD, 1, true);
			delay_s(5);
			clear_wdt();
	
			// Read voltage at input
			tmp_calc = adc_measure(VOLTIN_CHANNEL);
			tmp_calc *= 10000; // Multiply x by 10,000 so we can work with decimal numbers
			tmp_calc /= 1024; // Divide by 1024 to get % of A/D Converter range
			tmp_calc *= 5000; // Multiply by 5000
			tmp_calc /= 10000; // Divide by 10000 to get it in terms of voltage
			volt_in = tmp_calc;
			
			// Delay 5ms
			delay_ms(5);
			
			// Read voltage at output
			tmp_calc = adc_measure(VOLTOUT_CHANNEL);
			tmp_calc *= 10000; // Multiply x by 10,000 so we can work with decimal numbers
			tmp_calc /= 1024; // Divide by 1024 to get % of A/D Converter range
			tmp_calc *= 5000; // Multiply by 5000
			tmp_calc /= 10000; // Divide by 10000 to get it in terms of voltage
			volt_out = tmp_calc;
	
// TEMP
NMD[0] = chipid;
NMD[1] = (volt_in >> 8) & 0xFF;
NMD[2] = volt_in & 0xFF;
NMD[3] = (volt_out >> 8) & 0xFF;
NMD[4] = volt_out & 0xFF;
NMD[5] = (cell_voltage >> 8) & 0xFF;
NMD[6] = cell_voltage & 0xFF;
CANSendMessage( 0x3B, NMD, 7, CAN_TX_PRIORITY_3 & CAN_TX_STD_FRAME & CAN_TX_NO_RTR_FRAME);	

			// Determine if shunting is working
			if((volt_in - volt_out) < 240)
				shunting_fault = true;
	
			// Request cell stop shunting
			NMD[0] = 0;
			SerialSendMessage(ID_SHUNT, chipid, NMD, 1, true);
			delay_s(5);
			clear_wdt();
	
			// Read voltage at input and output points. Actual voltage = Vref / ( x / 1024);
			tmp_calc = adc_measure(VOLTIN_CHANNEL);
			tmp_calc *= 10000; // Multiply x by 10,000 so we can work with decimal numbers
			tmp_calc /= 1024; // Divide by 1024 to get % of A/D Converter range
			tmp_calc *= 5000; // Multiply by 5000
			tmp_calc /= 10000; // Divide by 10000 to get it in terms of voltage
			volt_in = tmp_calc;
			
			// Delay 5ms
			delay_ms(5);
			
			tmp_calc = adc_measure(VOLTOUT_CHANNEL);
			tmp_calc *= 10000; // Multiply x by 10,000 so we can work with decimal numbers
			tmp_calc /= 1024; // Divide by 1024 to get % of A/D Converter range
			tmp_calc *= 5000; // Multiply by 5000
			tmp_calc /= 10000; // Divide by 10000 to get it in terms of voltage
			volt_out = tmp_calc;
	
			// Determine if shunting is shorted or locked on
			if((volt_in - volt_out) > 16 && volt_in > volt_out)
				shunting_fault = true;
	
			// Turn off power to cell
			set_cell(cell, false);

// TEMP
NMD[0] = chipid;
NMD[1] = (volt_in >> 8) & 0xFF;
NMD[2] = volt_in & 0xFF;
NMD[3] = (volt_out >> 8) & 0xFF;
NMD[4] = volt_out & 0xFF;
NMD[5] = (cell_voltage >> 8) & 0xFF;
NMD[6] = cell_voltage & 0xFF;
CANSendMessage( 0x3C, NMD, 7, CAN_TX_PRIORITY_3 & CAN_TX_STD_FRAME & CAN_TX_NO_RTR_FRAME);	

// TEMP
if(comm_fault == true || shunting_fault == true || adc_fault == true) {
NMD[0] = cell;
NMD[1] = comm_fault;
NMD[2] = shunting_fault;
NMD[3] = adc_fault;
CANSendMessage( 0x3D, NMD, 4, CAN_TX_PRIORITY_3 & CAN_TX_STD_FRAME & CAN_TX_NO_RTR_FRAME);
}
		}
		
		// Reset variables
		board_num = 0;
	} // Do this forever

	return;
}

unsigned short adc_measure(char channel)
{
	// Clear the channel selection in adcon0
	adcon0 &= 11000111b;

	// Shift the channel value to the appropriate position
	channel = (channel & 0x07) << 3;
	
	// And move it to the appropriate position in adcon0
	adcon0 |= channel;

	// Wait the required acquisition time (datasheet)
	delay_10us(2);
	
	// Start the conversion
	adc_go = 1;
	
	// Wait until it is done
	while (adc_go);
	
	unsigned short retval;
	retval = (short)adresh;
	retval = retval << 8;
	retval |= adresl;

	return retval;
}